package org.caw.ws.impl;

import java.util.List;

import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import org.caw.domain.Row;
import org.caw.domain.RowList;
import org.caw.domain.descriptor.TableDescriptor;
import org.caw.domain.descriptor.TableDescriptorList;
import org.caw.exception.CawException;
import org.caw.hbase.admin.AdminManager;
import org.caw.hbase.explorer.DeleteManager;
import org.caw.hbase.explorer.GetManager;
import org.caw.hbase.explorer.ScanManager;
import org.caw.hbase.explorer.UpdateManager;
import org.caw.ws.TableService;
import org.caw.ws.domain.TableUpdateAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TableServiceImpl implements TableService {

	private static final Logger LOGGER = LoggerFactory.getLogger(TableServiceImpl.class);

	private AdminManager adminManager;

	private GetManager getManager;

	private ScanManager scanManager;

	private UpdateManager updateManager;

	private DeleteManager deleteManager;

	@Override
	public Response get(boolean details) {
		try {
			TableDescriptorList result = adminManager.getTableDescriptions(details);
			return Response.status(Status.OK).entity(result).build();
		} catch (CawException e) {
			LOGGER.error("Failed to get table statuses", e);
			return Response.status(Status.INTERNAL_SERVER_ERROR).build();
		}
	}

	@Override
	public Response get(String tableName) {
		try {
			TableDescriptor result = adminManager.getTableDescription(tableName);
			return Response.status(Status.OK).entity(result).build();
		} catch (CawException e) {
			LOGGER.error("Failed to get table status", e);
			return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e).build();
		}
	}

	@Override
	public Response create(String tableName, TableDescriptor descriptor, boolean overwrite) {
		descriptor.setName(tableName);
		try {
			return Response.status(Status.CREATED).entity(adminManager.createTable(descriptor, overwrite)).build();
		} catch (CawException e) {
			LOGGER.error("Failed to create table {}", tableName, e);
			return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e).build();
		}
	}

	@Override
	public Response delete(String tableName) {
		try {
			adminManager.deleteTable(tableName);
			return Response.status(Status.OK).build();
		} catch (CawException e) {
			LOGGER.error("Failed to delete table {}", tableName, e);
			return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e).build();
		}
	}

	@Override
	public Response update(String tableName, TableDescriptor descriptor) {
		descriptor.setName(tableName);
		try {
			return Response.status(Status.OK).entity(adminManager.updateTable(descriptor)).build();
		} catch (CawException e) {
			LOGGER.error("Failed to update table {}", tableName, e);
			return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e).build();
		}
	}

	@Override
	public Response update(String tableName, TableUpdateAction action) {
		try {
			switch (action) {
			case disable:
				adminManager.disableTable(tableName);
				break;

			case enable:
				adminManager.enableTable(tableName);
				break;

			case truncate:
				adminManager.trunateTable(tableName);
				break;
			}
			return Response.status(Status.OK).build();
		} catch (CawException e) {
			LOGGER.error("Failed to " + action + "} table {}", tableName, e);
			return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e).build();
		}
	}

	@Deprecated
	@Override
	public Response multiget(String tableName, List<String> rowkeys, List<String> columns, Integer maxversions, Long timestamp) {
		try {
			return Response.status(Status.OK).entity(executeMultiget(tableName, rowkeys, columns, maxversions, timestamp)).build();
		} catch (CawException e) {
			LOGGER.error("Failed to multiget from table {}", tableName, e);
			return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e).build();
		}
	}

	@Deprecated
	@Override
	public Response scan(String tableName, String filters, String startKey, String endKey, Integer limit, List<String> columns, Integer maxversions, Long timestamp) {
		try {
			RowList rowList = executeScan(tableName, filters, startKey, endKey, limit, columns, maxversions, timestamp);
			return Response.status(Status.OK).entity(rowList).build();
		} catch (CawException e) {
			LOGGER.error("Failed to scan table {}", tableName, e);
			return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e).build();
		}
	}

	private RowList executeScanAndMultiget(String tableName, String filter, String startKey, String endKey, Integer limit, List<String> rowkeys, List<String> columns, Integer maxversions, Long timestamp) throws CawException {
		RowList multigetRowList = null;

		if (rowkeys != null && !rowkeys.isEmpty()) {
			try {
				LOGGER.debug("Multiget parameters: tableName = " + tableName + ", rowkeys = " + rowkeys + ", columns = " + columns + ", maxversions = " + maxversions + ", timestamp = ", timestamp);
				multigetRowList = executeMultiget(tableName, rowkeys, columns, maxversions, timestamp);
				LOGGER.info("Successful multiget {} from table {}", rowkeys, tableName);
			} catch (CawException e) {
				// Handle any multiget exception
				LOGGER.error("Failed to execute multiget {} from table {}", rowkeys, tableName);
			}
		}

		RowList scanRowList = null;

		try {
			LOGGER.debug("Scan parameters: tableName = " + tableName + ", filter = " + filter + ", startKey = " + startKey + ", endKey = " + endKey + ", limit = " + limit + ", columns = ", columns + ", maxversions = " + maxversions + ", timestamp = " + timestamp);
			scanRowList = executeScan(tableName, filter, startKey, endKey, limit, columns, maxversions, timestamp);
		} catch (CawException e) {
			LOGGER.error("Failed to scan table {}", rowkeys, tableName);
			throw e;
		}

		if (multigetRowList != null && !multigetRowList.getRows().isEmpty()) {
			scanRowList.getRows().addAll(multigetRowList.getRows());
		}

		return scanRowList;
	}

	private RowList executeScan(String tableName, String filters, String startKey, String endKey, Integer limit, List<String> columns, Integer maxversions, Long timestamp) throws CawException {
		if (filters != null) {
			if (timestamp != null) {
				return scanManager.scan(tableName, startKey, endKey, columns, timestamp, limit, filters);
			} else {
				return scanManager.scan(tableName, startKey, endKey, columns, maxversions, limit, filters);
			}
		} else {
			if (timestamp != null) {
				return scanManager.scan(tableName, startKey, endKey, columns, timestamp, limit);
			} else {
				return scanManager.scan(tableName, startKey, endKey, columns, maxversions, limit);
			}
		}
	}

	private RowList executeMultiget(String tableName, List<String> rowkeys, List<String> columns, Integer maxversions, Long timestamp) throws CawException {
		if (timestamp != null) {
			return getManager.multiget(tableName, rowkeys, columns, timestamp);
		} else {
			return getManager.multiget(tableName, rowkeys, columns, maxversions);
		}
	}

	@Override
	public Response getRows(String tableName, String filter, String startKey, String endKey, Integer limit, List<String> rowkeys, List<String> columns, Integer maxversions, Long timestamp) {
		try {
			RowList rowList = executeScanAndMultiget(tableName, filter, startKey, endKey, limit, rowkeys, columns, maxversions, timestamp);
			return Response.status(Status.OK).entity(rowList).build();
		} catch (CawException e) {
			LOGGER.error("Failed to scan from table {}", tableName, e);
			return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e).build();
		}
	}

	@Override
	public Response get(String tableName, String rowkey, List<String> columns, Integer maxversions, Long timestamp) {
		try {
			Row row = null;
			if (timestamp != null) {
				row = getManager.get(tableName, rowkey, columns, timestamp);
			} else {
				row = getManager.get(tableName, rowkey, columns, maxversions);
			}
			return Response.status(Status.OK).entity(row).build();
		} catch (CawException e) {
			LOGGER.error("Failed to get record with rowkey {} from table " + tableName, rowkey, e);
			return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e).build();
		}
	}

	@Override
	public Response delete(String tableName, String rowkey, Long timestamp, List<String> columns) {
		try {
			deleteManager.delete(tableName, rowkey, columns, timestamp);
			LOGGER.info("Record with rowkey {} successfully deleted from table {}", rowkey, tableName);
			return Response.status(Status.OK).build();
		} catch (CawException e) {
			LOGGER.error("Failed to delete record with rowkey {} from table " + tableName, rowkey, e);
			return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e).build();
		}
	}

	@Override
	public Response create(String tableName, String rowkey, Row row, boolean overwrite) {
		try {
			row.setRowKey(rowkey);
			updateManager.create(tableName, rowkey, row, overwrite);
			LOGGER.info("Record with rowkey {} successfully created in table {}", rowkey, tableName);
			return Response.status(Status.CREATED).build();
		} catch (CawException e) {
			LOGGER.error("Failed to create/overwrite record with rowkey {} in table " + tableName, rowkey, e);
			return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e).build();
		}
	}

	@Override
	public Response update(String tableName, String rowkey, Row row) {
		try {
			row.setRowKey(rowkey);
			updateManager.update(tableName, rowkey, row);
			LOGGER.info("Record with rowkey {} successfully updated in table {}", rowkey, tableName);
			return Response.status(Status.OK).build();
		} catch (CawException e) {
			LOGGER.error("Failed to update record with rowkey {} in table " + tableName, rowkey, e);
			return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e).build();
		}
	}

	public AdminManager getAdminManager() {
		return adminManager;
	}

	public void setAdminManager(AdminManager adminManager) {
		this.adminManager = adminManager;
	}

	public GetManager getGetManager() {
		return getManager;
	}

	public void setGetManager(GetManager getManager) {
		this.getManager = getManager;
	}

	public ScanManager getScanManager() {
		return scanManager;
	}

	public void setScanManager(ScanManager scanManager) {
		this.scanManager = scanManager;
	}

	public UpdateManager getUpdateManager() {
		return updateManager;
	}

	public void setUpdateManager(UpdateManager updateManager) {
		this.updateManager = updateManager;
	}

	public DeleteManager getDeleteManager() {
		return deleteManager;
	}

	public void setDeleteManager(DeleteManager deleteManager) {
		this.deleteManager = deleteManager;
	}
}
